////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "WNMemory/inc/WNMemory.h"
#include "WNMath/inc/WNBounds2.h"
#include "WNMath/inc/WNBasic.h"
#include "WNMath/inc/WNExtended.h"
#include "WNMath/inc/WNRectangle.h"
#include "WNMath/inc/WNCircle.h"
#include "WNMath/inc/WNVector2.h"

using namespace WNMath;
using namespace WNMemory;

// Constructors ////////////////////////////////////////////////////////////////////////////////////

WNBounds2::WNBounds2() :
    mMinimum(0.0f, 0.0f),
    mMaximum(0.0f, 0.0f) {
}

WNBounds2::WNBounds2(const WN_FLOAT32* _f) :
    mMinimum(_f[0], _f[1]),
    mMaximum(_f[2], _f[3]) {
}

WNBounds2::WNBounds2(const WNPoint2& _point) :
    mMinimum(_point),
    mMaximum(_point) {
}

WNBounds2::WNBounds2(const WNPoint2& _min, const WNPoint2& _max) :
    mMinimum(_min),
    mMaximum(_max) {
}

// Operators ///////////////////////////////////////////////////////////////////////////////////////

WN_BOOL WNBounds2::operator == (const WNBounds2& _bounds) const {
    return(0 == WNMemCmp(this, &_bounds, sizeof(WNBounds2)));
}

WN_BOOL WNBounds2::operator != (const WNBounds2& _bounds) const {
    return(0 != WNMemCmp(this, &_bounds, sizeof(WNBounds2)));
}

// Members /////////////////////////////////////////////////////////////////////////////////////////

WN_VOID WNBounds2::Zero() {
    mMinimum.Zero();
    mMaximum.Zero();
}

WN_FLOAT32 WNBounds2::Length() const {
    const WNVector2 size = mMaximum - mMinimum;

    return(WNAbs(size.x));
}

WN_FLOAT32 WNBounds2::Width() const {
    const WNVector2 size = mMaximum - mMinimum;

    return(WNAbs(size.y));
}

WN_FLOAT32 WNBounds2::Perimeter() const {
    const WNVector2 size = mMaximum - mMinimum;

    return(WNAbs(2.0f * size.x + 2.0f * size.y));
}

WN_FLOAT32 WNBounds2::Radius() const {
    WN_FLOAT32 total = 0.0f;

    for (WN_INT32 i = 0; i < 2; i++) {
        const WN_FLOAT32 minComponent = WNAbs(mMinimum[i]);
        const WN_FLOAT32 maxComponent = WNAbs(mMaximum[i]);

        if (minComponent > maxComponent) {
            total += minComponent * minComponent;
        } else {
            total += maxComponent * maxComponent;
        }
    }

    return(WNSqrt(total));
}

WN_FLOAT32 WNBounds2::Area() const {
    const WNVector2 size = mMaximum - mMinimum;

    return(WNAbs(size.x * size.y));
}

WN_BOOL WNBounds2::IsZero() const {
    return(mMinimum.IsZero() && mMaximum.IsZero());
}

WN_BOOL WNBounds2::IsInsideOut() const {
    return(mMinimum.x > mMaximum.x || mMinimum.y > mMaximum.y);
}

WN_VOID WNBounds2::Expand(WN_FLOAT32 _amount) {
    const WNVector2 expand = WNVector2(_amount);

    mMaximum += expand;
    mMinimum -= expand;
}

WN_BOOL WNBounds2::Expand(const WNPoint2& _point) {
    WN_BOOL expanded = WN_FALSE;

    if (_point.x < mMinimum.x) {
        mMinimum.x = _point.x;

        expanded = WN_TRUE;
    }

    if (_point.x > mMaximum.x) {
        mMaximum.x = _point.x;

        expanded = WN_TRUE;
    }

    if (_point.y < mMinimum.y) {
        mMinimum.y = _point.y;

        expanded = WN_TRUE;
    }

    if (_point.y > mMaximum.y) {
        mMaximum.y = _point.y;

        expanded = WN_TRUE;
    }

    return(expanded);
}

WN_BOOL WNBounds2::Expand(const WNRectangle& _rectangle) {
    const WNVector2 extents = _rectangle.extents.GetRotated(_rectangle.rotation);
    const WNPoint2 point1 = _rectangle.location + extents;
    const WNPoint2 point2 = _rectangle.location + WNVector2(-extents.x, extents.y);
    const WNPoint2 point3 = _rectangle.location + WNVector2(extents.x, -extents.y);
    const WNPoint2 point4 = _rectangle.location - extents;

    return(Expand(point1) || Expand(point2) || Expand(point3) || Expand(point4));
}

WN_BOOL WNBounds2::Expand(const WNCircle& _circle) {
    const WNPoint2 location = _circle.mLocation;
    const WN_FLOAT32 radius = _circle.mRadius;

    return(Expand(WNPoint2(location.x + radius, location.y)) || Expand(WNPoint2(location.x - radius, location.y)) ||
           Expand(WNPoint2(location.x, location.y + radius)) || Expand(WNPoint2(location.x, location.y - radius)));
}

WN_BOOL WNBounds2::Expand(const WNBounds2& _bounds) {
    WN_BOOL expanded = WN_FALSE;

    if (_bounds.mMinimum.x < mMinimum.x) {
        mMinimum.x = _bounds.mMinimum.x;

        expanded = WN_TRUE;
    }

    if (_bounds.mMinimum.y < mMinimum.y) {
        mMinimum.y = _bounds.mMinimum.y;

        expanded = WN_TRUE;
    }

    if (_bounds.mMaximum.x > mMaximum.x) {
        mMaximum.x = _bounds.mMaximum.x;

        expanded = WN_TRUE;
    }

    if (_bounds.mMaximum.y > mMaximum.y) {
        mMaximum.y = _bounds.mMaximum.y;

        expanded = WN_TRUE;
    }

    return(expanded);
}

WN_VOID WNBounds2::Translate(const WNVector2& _vector) {
    mMinimum += _vector;
    mMaximum += _vector;
}

WNBounds2 WNBounds2::GetExpanded(WN_FLOAT32 _amount) const {
    WNBounds2 bounds = *this;

    return(bounds.Expand(_amount), bounds);
}

WNBounds2 WNBounds2::GetExpanded(const WNPoint2& _point) const {
    WNBounds2 bounds = *this;

    return(bounds.Expand(_point), bounds);
}

WNBounds2 WNBounds2::GetExpanded(const WNRectangle& _rectangle) const {
    WNBounds2 bounds = *this;

    return(bounds.Expand(_rectangle), bounds);
}

WNBounds2 WNBounds2::GetExpanded(const WNCircle& _circle) const {
    WNBounds2 bounds = *this;

    return(bounds.Expand(_circle), bounds);
}

WNBounds2 WNBounds2::GetExpanded(const WNBounds2& _bounds) const {
    WNBounds2 bounds = *this;

    return(bounds.Expand(_bounds), bounds);
}

WNBounds2 WNBounds2::GetTranslated(const WNVector2& _vector) const {
    WNBounds2 bounds = *this;

    return(bounds.Translate(_vector), bounds);
}

WN_VOID WNBounds2::GetPoints(WNPoint2* _points) const {
    _points[0] = mMaximum;
    _points[1] = (mMinimum * WNPoint2(0.0f, 1.0f) + mMaximum * WNPoint2(1.0f, 0.0f)).ToPoint2();
    _points[2] = (mMinimum * WNPoint2(1.0f, 0.0f) + mMaximum * WNPoint2(0.0f, 1.0f)).ToPoint2();
    _points[3] = mMinimum;
}

WNRectangle WNBounds2::ToRectangle() const {
    const WNPoint2 location = WNMidPoint(mMinimum, mMaximum);
    const WNVector2 extents = mMaximum - location;

    return(WNRectangle(location, extents, 0.0f));
}